const CP = require('child_process')
const Path = require('path')
const { nanoid } = require('nanoid')
const _ = require('lodash')
const vue = require('vue')

/**
 *
 * @returns {number}在nodejs或者js中返回Cpu数量
 */
function cpus() {
  try {
    return Math.max(1, require('os').cpus().length)
  } catch (error) {
    return Math.max(1, navigator.hardwareConcurrency)
  }
}

/**
 * 用子进程写入csv
 *
 * @param {string}path 文件路径
 * @param {object[]}rows (可选)首次导入的数据
 * @returns {object} .worker worker, setTitles(titles: string[]) 标题
 */
function csvAcc(path, rows) {
  const worker = CP.fork(Path.join(__dirname, './csv-Accumulator.js'))
  worker.setMaxListeners(Infinity)
  worker.send({ path })
  const work = {
    worker,
    setTitles(titles) {
      worker.send({ titles })
    },
    push(...lines) {
      return new Promise((resolve) => {
        const id = nanoid()
        worker.on(
          'message',
          ({ complete, id: idCheck }) => complete && idCheck === id && resolve()
        )
        worker.send({ lines, id })
      })
    },
    terminate: () => worker.kill(2),
    kill: () => worker.kill(2)
  }
  rows && work.push(...rows)
  return work
}

/**
 * @param {string}path 路径
 * @param {*}obj (可选)可以转为json的内容
 * @returns {object} {worker,kill,terminate,write}
 */
function jsonWriter(path, obj) {
  const Path = require('path')
  const worker = CP.fork(Path.join(__dirname, './json-Writer.js'))
  worker.setMaxListeners(Infinity)
  worker.send({ path })
  const work = {
    worker: worker,
    kill: () => worker.kill(2),
    terminate: () => worker.kill(2),
    write: (...args) =>
      new Promise((resolve) => {
        const id = nanoid()
        worker.on(
          'message',
          ({ complete, id: id1 }) => id1 === id && complete && resolve()
        )
        let postMsg = {
          path: _.find(args, (e) =>
            /string/i.test(Object.prototype.toString.call(e))
          ),
          json: _.find(
            args,
            (e) => !/string/i.test(Object.prototype.toString.call(e))
          ),
          id
        }
        worker.send(postMsg)
        postMsg = null // 释放内存
      })
  }
  obj && work.write(obj)
  work.output = work.write
  return work
}

/**
 * @param {string}path 路径
 * @param {object[] | Array[String[]]}data *
 * @returns {object} {worker,kill,terminate,write}
 */
function xlsxWriter(path, data) {
  const Path = require('path')
  const worker = CP.fork(Path.join(__dirname, 'xlsx-Writer.js'))
  worker.setMaxListeners(Infinity)
  worker.send({ path })
  const work = {
    worker: worker,
    kill: () => worker.kill(2),
    terminate: () => worker.kill(2),
    write: (...args) =>
      new Promise((resolve) => {
        const id = nanoid()
        worker.on(
          'message',
          ({ complete, id: id1 }) => id1 === id && complete && resolve()
        )
        let postMsg = {
          path: _.find(args, (e) =>
            /string/i.test(Object.prototype.toString.call(e))
          ),
          json: _.find(
            args,
            (e) => !/string/i.test(Object.prototype.toString.call(e))
          ),
          id
        }
        worker.send(postMsg)
        postMsg = null // 释放内存
      })
  }
  data && work.write(data)
  work.output = work.write
  return work
}
/**
 * 创建一个json写入线程
 *
 * @param {string}path 要导出的json文件路径
 * @param {*[] | object}rows 首次要导入的数据
 * @returns {object}返回{worker,kill,terminate,assign}
 */
function jsonAcc(path, rows) {
  const { nanoid } = require('nanoid')
  const cp = require('child_process')
  const Path = require('path')
  const worker = cp.fork(Path.join(__dirname, './json-Accumulator.js'))
  worker.setMaxListeners(Infinity)
  path && worker.send({ path })
  const work = {
    worker: worker,
    kill: () => worker.kill(2),
    terminate: () => worker.kill(2),
    assign: (...args) =>
      new Promise((resolve) => {
        const id = nanoid()
        worker.on('message', ({ id: id1 }) => id1 === id && resolve())
        // 向worker推送rows
        // let postMsg = { path, rows, id }
        let postMsg = {
          path: _.find(args, (e) =>
            /string/i.test(Object.prototype.toString.call(e))
          ),
          rows: _.find(
            args,
            (e) => !/string/i.test(Object.prototype.toString.call(e))
          ),
          id
        }
        worker.send(postMsg)
        postMsg = null // 释放内存
      })
  }
  rows && work.assign(rows)
  work.push = work.append = work.assign
  return work
}

/**
 * 对任务进行最优化队列
 *
 * @param {object[]}tasks 任务队列
 * @param {Function}tasks.func 该任务所用函数，返回值必须是Promise
 * @param {*[]}tasks.args 该任务所用函数的参数
 * @param {string | null}tasks.key 该任务的返回值如果是Object, 其中有一个key是Promise的话，key的名称
 * @param {Function}tasks.then 回调该任务返回值[key]的promise
 * @param {Function}[watcher=console.log] 回调{waiting, finished, processing, total}
 * @param {number}[maxChunk=cpus()] 队列最大长度，默认为cpu数量
 * @returns {Promise<*[]>} 返回运行结果数组
 */
function queque(tasks = [], watcher = console.log, maxChunk = cpus()) {
  return new Promise((resolve) => {
    const beforeProcessing = vue.reactive(
      // 未开始的任务
      [...tasks].map((obj, index) => {
        obj.id = nanoid()
        obj.index = index
        return obj
      })
    )
    const total = tasks.length
    const processing = vue.reactive([]) // 执行中的任务
    const results = vue.reactive([])
    const finished = vue.computed(
      () => total - processing.length - beforeProcessing.length
    )
    const progress = vue.computed(
      () => `${~~((finished.value * 1000) / total) / 10}%`
    )
    const detail = vue.computed(
      () => `${finished.value}/${total} (${progress})`
    )
    vue.watchEffect(() => {
      watcher &&
        watcher({
          waiting: beforeProcessing.length,
          finished: finished.value,
          processing: processing.length,
          progress,
          detail,
          total
        })
    })
    vue.watchEffect(
      () =>
        processing.length + beforeProcessing.length === 0 && resolve(results)
    )
    vue.watchEffect(() => {
      while (processing.length < maxChunk && beforeProcessing.length > 0) {
        let {
          func = async () => 0,
          args = [],
          key = null,
          then = () => 0,
          index
        } = beforeProcessing.shift() // 提取出waiting的一个元素
        const id = nanoid()
        const job = vue.reactive({
          id,
          promise: key ? func(...args)[key] : func(...args)
        })
        processing.push(job) // 把waiting的最后一个元素充进去
        // eslint-disable-next-line promise/catch-or-return
        job.promise.then((result) => {
          // 如果该promise已经fulfill
          _.pullAt(
            // 删除该元素
            processing,
            _.findIndex(processing, ({ id: idCheck }) => id === idCheck) // 找到一个符合该id的job，然后删除
          )
          results[index] = result
          return then(result) // 回调结果
        })
        args = null // args可能比较大，所以清空内存
      }
    })
  })
}

module.exports = {
  csvAcc,
  queque,
  jsonAcc,
  jsonWriter,
  xlsxWriter
}
